import cv2
import numpy as np
import os
import threading
import queue
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import tkinter as tk
from tkinter import ttk, Label
import time
import json
import subprocess
import json
import requests
import re

# 检查OpenCL支持
opencl_available = False
try:
    if cv2.ocl.haveOpenCL():
        cv2.ocl.setUseOpenCL(True)
        opencl_available = cv2.ocl.useOpenCL()
        print("OpenCL is available. Using GPU acceleration.")
    else:
        print("OpenCL is not available. Using CPU processing.")
except:
    print("OpenCL is not available. Using CPU processing.")

frame_queue = queue.Queue(maxsize=20)  # 增加缓冲大小
result_queue = queue.Queue(maxsize=20)  # 增加缓冲大小
diameter_data = []
time_data = []
detection_active = False
file_list = []  # 存储所有结果文件路径（按编号排序）
current_index = -1  # 当前选中的文件索引

# 全局变量用于存储分析结果
last_analysis_result = ""

# 全局状态变量：标记AI分析是否正在进行
analysis_in_progress = False

# 全局变量用于存储开始时间
start_time = time.time()

# 添加内存管理优化
frame_skip_counter = 0
FRAME_SKIP_INTERVAL = 2  # 每2帧处理一次，提高帧率

def process_frame():
    global start_time, frame_skip_counter
    
    while True:
        frame = frame_queue.get()
        if frame is None:
            break
            
        frame_skip_counter += 1
        if frame_skip_counter % FRAME_SKIP_INTERVAL != 0:
            # 跳过部分帧以提高处理速度
            continue
            
        # 根据OpenCL可用性选择处理方式
        if opencl_available:
            # 使用UMat进行OpenCL加速处理
            frame_umat = cv2.UMat(frame)
            # 转换为灰度图
            gray_umat = cv2.cvtColor(frame_umat, cv2.COLOR_BGR2GRAY)
            # 二值化处理 - 使用更高效的阈值
            _, binary_umat = cv2.threshold(gray_umat, 60, 255, cv2.THRESH_BINARY)
            # 转换回普通数组以进行显示
            binary = cv2.UMat.get(binary_umat)
            # UMat对象会自动释放内存，无需手动调用release()
        else:
            # 转换为灰度图
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            # 二值化处理 - 使用更高效的阈值
            _, binary = cv2.threshold(gray, 40, 255, cv2.THRESH_BINARY)
            
        # 创建彩色图层用于显示
        display = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)
        
        # 仅在检测激活时执行圆检测
        if detection_active:
            # 使用霍夫圆变换检测圆 - 优化参数提高性能
            circles = cv2.HoughCircles(binary, cv2.HOUGH_GRADIENT, dp=2, minDist=400,
                                      param1=45, param2=8, minRadius=60, maxRadius=140)
            
            # 只处理检测到的一个圆
            if circles is not None and len(circles[0]) > 0:
                circles = np.uint16(np.around(circles))
                circle = circles[0][0]  # 只取第一个圆
                
                # 绘制圆和圆心
                cv2.circle(display, (circle[0], circle[1]), circle[2], (0, 255, 0), 2)
                cv2.circle(display, (circle[0], circle[1]), 2, (0, 255, 0), 3)
                
                # 显示直径(像素)
                diameter = circle[2] * 2
                cv2.putText(display, f"Diameter: {diameter}px", (10, 30), 
                            cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
                
                # 改进的阈值过滤和平滑处理
                if not hasattr(process_frame, 'window'):
                    process_frame.window = []
                    process_frame.prev_diameter = diameter  # 初始化为第一个有效直径
                    process_frame.outlier_count = 0  # 异常值计数器
                elif process_frame.prev_diameter is None:
                    process_frame.prev_diameter = diameter
                
                # 使用中值作为参考基准，提高稳定性
                if len(process_frame.window) > 0:
                    # 创建包含当前检测值的临时窗口用于中值计算
                    temp_window = process_frame.window + [diameter]
                    temp_window.sort()
                    median_diameter = temp_window[len(temp_window) // 2]
                    
                    # 自适应阈值计算
                    threshold = max(3, min(25, median_diameter * 0.08))
                    
                    # 判断当前检测值是否在合理范围内
                    if abs(diameter - median_diameter) <= threshold:
                        valid_diameter = diameter
                        process_frame.outlier_count = 0  # 重置异常值计数器
                    else:
                        # 异常值处理
                        process_frame.outlier_count += 1
                        # 如果连续3个异常值，则认为是真实变化，接受当前值
                        if process_frame.outlier_count >= 3:
                            valid_diameter = diameter
                            process_frame.outlier_count = 0  # 重置计数器
                            print(f"检测到持续变化，接受新值: {diameter}px")
                        else:
                            # 否则保持上一个有效值
                            valid_diameter = process_frame.prev_diameter
                else:
                    # 初始化阶段，直接接受第一个检测值
                    valid_diameter = diameter
                    process_frame.outlier_count = 0
                
                # 更新prev_diameter为当前有效值
                process_frame.prev_diameter = valid_diameter
                
                # 滑动平均（窗口大小3）
                process_frame.window.append(valid_diameter)
                if len(process_frame.window) > 3:
                    process_frame.window.pop(0)
                smoothed_diameter = sum(process_frame.window) / len(process_frame.window)  # 使用浮点数计算
                
                # 添加调试输出
                print(f"Original diameter: {diameter}px, Effective diameter: {valid_diameter}px, Smooth diameter: {smoothed_diameter:.2f}px")
                
                # 记录平滑后的数据
                diameter_data.append(smoothed_diameter)
                time_data.append(time.time() - start_time)
        
        result_queue.put(display)

def update_preview(frame):
    # 将OpenCV图像转换为Tkinter可用的格式
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame = cv2.resize(frame, (480, 360))  # 调整图像大小以适应预览窗口
    img = Image.fromarray(frame)
    imgtk = ImageTk.PhotoImage(image=img)
    preview_label.imgtk = imgtk
    preview_label.configure(image=imgtk)

def update_plot(frame):
    # 更新图表数据
    ax.clear()
    if diameter_data:
        ax.plot(time_data, diameter_data, 'b-')
        ax.set_ylim(min(diameter_data)*0.9, max(diameter_data)*1.1)
    ax.set_xlabel('Time (s)')
    ax.set_ylabel('Diameter (px)')
    ax.set_title('Real-time Diameter Measurement')
    ax.grid(True)
    canvas.draw()

def start_detection():
    global detection_active, start_time, analysis_in_progress, start_btn, status_label
    if not analysis_in_progress:
        detection_active = True
        file_label.pack_forget()  # 检测时隐藏标签
        diameter_data.clear()
        time_data.clear()
        start_time = time.time()
        # 优化滑动窗口初始化（保留最近3个历史值或初始化为稳定值）
        if hasattr(process_frame, 'window'):
            # 保留最后3个有效值作为初始窗口，若无则初始化为[median_diameter]*3
            if len(process_frame.window) >= 3:
                process_frame.window = process_frame.window[-3:]
            else:
                # 假设初始稳定直径为100px（根据实际场景调整）
                process_frame.window = [100.0] * 3
                process_frame.prev_diameter = 100.0
        update_status_label("检测中...", 'blue')
    else:
        update_status_label("分析中，无法启动新检测", 'red')
        process_frame.prev_diameter = None
    
    # 重置图表
    ax.cla()
    ax.set_xlabel('Time (s)')
    ax.set_ylabel('Diameter (px)')
    ax.set_title('Real-time Diameter Measurement')
    ax.grid(True)
    canvas.draw()
    
    # 在单独线程中运行检测循环，避免阻塞主窗口
    def detection_loop():
        global detection_active
        # 检测持续时间（秒）
        DETECTION_DURATION = 5
        # 定义检测持续时间（秒）
        end_time = start_time + DETECTION_DURATION
        
        # 添加内存监控
        last_cleanup_time = time.time()
        
        while time.time() < end_time and detection_active:
            current_time = time.time()
            
            # 定期清理内存（每2秒）
            if current_time - last_cleanup_time > 2:
                # 强制垃圾回收
                import gc
                gc.collect()
                last_cleanup_time = current_time
            
            if not result_queue.empty():
                # 仅处理最新帧，避免旧数据堆积
                display = None
                while not result_queue.empty():
                    if display is not None:
                        # 释放之前的帧内存
                        del display
                    display = result_queue.get()  # 持续获取直到队列为空，保留最后一帧
                
                if display is not None:
                    # 通过主线程调度更新预览（避免跨线程UI操作）
                    root.after(0, update_preview, display)  # 0ms延迟立即执行
            
            # 降低循环频率避免CPU占用过高
            time.sleep(0.033)  # 调整为30Hz循环频率（33ms间隔），降低CPU占用
        
        # 检测结束后清理内存
        import gc
        gc.collect()
        
        # 检测结束后一次性更新图表
        if diameter_data:
            ax.cla()
            ax.plot(time_data, diameter_data, 'b-')
            ax.set_xlabel('Time (s)')
            ax.set_ylabel('Diameter (px)')
            ax.set_title('Real-time Diameter Measurement')
            ax.grid(True)
            if len(diameter_data) > 1:
                ax.set_ylim(min(diameter_data)*0.9, max(diameter_data)*1.1)
            ax.relim()
            ax.autoscale_view()
            canvas.draw()
            
        # 检测结束后进行AI分析
        if not analysis_in_progress and diameter_data:
            # 调用本地模型进行分析（异步执行）
            threading.Thread(target=process_analysis, args=(diameter_data,), daemon=True).start()
    
        detection_active = False
    
    # 启动检测循环
    threading.Thread(target=detection_loop, daemon=True).start()

# 更新状态标签的函数
def update_status_label(text, color='black'):
    status_label.config(text=text, foreground=color)

def process_analysis(diameters):
    global analysis_in_progress, start_btn, status_label, analysis_label, last_analysis_result
    analysis_in_progress = True
    start_btn.config(state=tk.DISABLED)
    update_status_label("分析中...", 'orange')
    try:
        analysis_result = analyze_with_llm(diameters)
        print("瞳孔直径分析结果：\n", analysis_result)
        # 保存分析结果到全局变量
        last_analysis_result = analysis_result
        # 更新模型分析结果显示标签
        analysis_label.config(text=f"模型分析结果：{analysis_result}")
        # 移除自动保存，只在点击保存按钮时保存
        update_status_label("分析完成", 'green')
    finally:
        analysis_in_progress = False
        start_btn.config(state=tk.NORMAL)

def analyze_with_llm(diameters):
    """使用HTTP API调用本地Qwen3模型分析瞳孔直径数据"""
    import requests
    
    if not diameters:
        return "无有效数据进行分析"
    
    # 构建分析提示词
    prompt = f"分析以下瞳孔直径随光变化的数据（单位：px），根据正常人类瞳孔对光反应分析瞳孔状态，给出一个包含生理性分析的简洁总结，不超过50字。\n"
    prompt += f"数据序列：{[int(d) for d in diameters[-20:]]}（共{len(diameters)}个数据点）"
    
    try:
        # 构建HTTP请求
        url = "http://127.0.0.1:9090/v1/chat/completions"
        headers = {
            "Content-Type": "application/json"
        }
        
        payload = {
            "model": "Qwen3-0.6B",
            "messages": [
                {
                    "role": "user",
                    "content": prompt
                }
            ],
            "stream": False
        }
        
        # 发送HTTP POST请求（增加超时时间）
        response = requests.post(url, headers=headers, json=payload, timeout=120)
        
        if response.status_code == 200:
            result = response.json()
            # 提取模型回复内容
            content = result['choices'][0]['message']['content'].strip()
            
            # 过滤掉think标签内容
            # 移除<think>...</think>标签及其内容
            content = re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL).strip()
            # 移除剩余的think相关标记
            content = re.sub(r'</?think>', '', content).strip()
            
            # 清理输出，提取关键分析结果
            # 优先查找"结论："或"总结："后的内容
            for keyword in ["结论：", "总结：", "分析："]:
                if keyword in content:
                    content = content.split(keyword)[-1].strip()
                    break
            
            # 去除多余空白和换行
            content = ' '.join(content.split())
            
            # 返回前50个字符，确保包含关键信息
            return content[:50] if len(content) > 50 else content
        else:
            return f"API调用失败：{response.status_code} - {response.text[:100]}"
            
    except requests.exceptions.Timeout:
        return "API响应超时"
    except requests.exceptions.ConnectionError:
        return "无法连接到本地API服务器，请确保服务在127.0.0.1:9090运行"
    except Exception as e:
        return f"API调用错误：{str(e)}"

def save_results(analysis_result=None):
    # 如果没有提供分析结果，则尝试从全局变量获取
    if analysis_result is None:
        analysis_result = last_analysis_result if last_analysis_result else "未进行分析"
    
    # 查找当前最大的文件编号
    max_num = 0
    for file in os.listdir('.'):
        if file.endswith('.json') and file[:-5].isdigit():
            num = int(file[:-5])
            if num > max_num:
                max_num = num
    new_num = max_num + 1
    save_file = f"{new_num}.json"
    # 将numpy类型转换为Python原生类型
    data = {
        'time': time_data,
        'diameter': [int(d) for d in diameter_data],
        'analysis': analysis_result
    }
    with open(save_file, 'w') as f:
        json.dump(data, f)
    print(f"Data saved to {save_file}")
    # 更新文件列表
    update_file_list()

def update_file_list():
    global file_list, current_index
    # 获取所有数字命名的JSON文件
    file_list = []
    for file in os.listdir('.'):
        if file.endswith('.json') and file[:-5].isdigit():
            file_list.append(file)
    # 按编号排序
    file_list.sort(key=lambda x: int(x[:-5]))
    # 如果有文件，current_index设为最后一个（最新保存的）
    if file_list:
        current_index = len(file_list) - 1
    else:
        current_index = -1

def load_previous():
    global current_index
    if current_index > 0:
        current_index -= 1
        load_file(file_list[current_index])

def load_next():
    global current_index
    if current_index < len(file_list) - 1:
        current_index += 1
        load_file(file_list[current_index])

def load_file(filename):
    try:
        with open(filename, 'r') as f:
            data = json.load(f)
            global time_data, diameter_data
            time_data = data['time']
            diameter_data = data['diameter']
            # 加载分析结果（如果存在）
            analysis_result = data.get('analysis', "未进行分析")
            update_plot(0)
            canvas.draw()
            print(f"Data loaded from {filename}")
            file_label.config(text=f"          Now File:{filename}")  # 更新标签内容
            file_label.pack()  # 显示标签
            # 更新分析结果显示
            analysis_label.config(text=f"模型分析结果：{analysis_result}")
    except FileNotFoundError:
        print("No saved data found")
    except json.JSONDecodeError as e:
        print(f"ERROR: {filename} ,INCORRECT FORMAT,UNABLE TO PARSE.ERROR INFO:{e}")

def delete_results():
    """删除当前加载的JSON文件"""
    global current_index, file_list
    
    # 检查是否有文件可以删除
    if current_index >= 0 and current_index < len(file_list):
        filename = file_list[current_index]
        try:
            os.remove(filename)
            print(f"Deleted {filename}")
            
            # 更新文件列表
            update_file_list()
            
            # 更新显示
            if file_list:
                # 如果还有其他文件，加载下一个或上一个
                if current_index < len(file_list):
                    load_file(file_list[current_index])
                elif current_index > 0:
                    current_index -= 1
                    load_file(file_list[current_index])
                else:
                    # 没有更多文件
                    clear_display()
            else:
                # 没有更多文件
                clear_display()
                
        except Exception as e:
            print(f"Error deleting {filename}: {e}")
    else:
        print("No file to delete")

def clear_display():
    """清空显示内容"""
    global time_data, diameter_data
    time_data = []
    diameter_data = []
    update_plot(0)
    canvas.draw()
    file_label.config(text="")
    analysis_label.config(text="模型分析结果将显示在这里")
    print("Display cleared")

def exit_program():
    root.destroy()
    os._exit(0)

if __name__ == "__main__":
    update_file_list()  # 初始化文件列表
    import tkinter as tk
    from tkinter import ttk, Label
    from PIL import Image, ImageTk
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    
    # 创建主窗口
    root = tk.Tk()
    root.title('Opticeye Circle Detection System')
    root.geometry('1024x700')  # 设置初始尺寸
    root.minsize(800, 600)  # 设置最小尺寸
    
    # 定义基准尺寸
    BASE_WIDTH = 1024
    BASE_HEIGHT = 600
    
    # 存储组件引用以便调整大小
    components = {}
    
    # 计算缩放比例的函数
    def get_scale_factors():
        current_width = root.winfo_width()
        current_height = root.winfo_height()
        if current_width == 1 and current_height == 1:  # 初始状态
            current_width = BASE_WIDTH
            current_height = BASE_HEIGHT
        width_scale = current_width / BASE_WIDTH
        height_scale = current_height / BASE_HEIGHT
        return width_scale, height_scale
    
    # 根据窗口大小调整界面元素
    def adjust_ui_elements():
        # 避免在窗口最小化时调整元素
        if root.winfo_width() < 10 or root.winfo_height() < 10:
            return
        
        width_scale, height_scale = get_scale_factors()
        scale = min(width_scale, height_scale)  # 使用较小的缩放比例保持比例
        
        # 统一字体缩放因子，确保所有字体以相同比例缩放
        font_scale = scale
        
        # 调整按钮样式
        style = ttk.Style()
        button_font_size = max(int(16 * font_scale), 8)  # 确保字体不小于8
        button_padding = int(8 * scale)
        style.configure('TButton', font=('Arial', button_font_size), padding=button_padding)
        
        # 调整按钮的内边距
        for button in components['buttons']:
            button.grid_configure(padx=max(int(12 * scale), 2), pady=max(int(6 * scale), 2))
        
        # 调整标签字体，使用统一的缩放因子
        status_font_size = max(int(20 * font_scale), 14)  # 与静态设置保持一致
        analysis_font_size = max(int(18 * font_scale), 10)
        file_font_size = max(int(16 * font_scale), 12)  # 与静态设置保持一致
        
        # 更新各标签的字体
        components['status_label'].config(font=('Arial', status_font_size))
        components['analysis_label'].config(font=('Arial', analysis_font_size))
        components['file_label'].config(font=('Arial', file_font_size))
        
        # 调整框架尺寸
        win_width = root.winfo_width()
        win_height = root.winfo_height()
        if win_width == 1 and win_height == 1:  # 初始状态
            win_width = BASE_WIDTH
            win_height = BASE_HEIGHT
            
        half_width = int(win_width * 0.5)  # 50%宽度
        half_height = int(win_height * 0.5)  # 50%高度
        top_height = int(win_height * 0.65)  # 上方区域65%高度
        bottom_height = int(win_height * 0.35)  # 下方区域35%高度
        
        # 更新各框架的位置和尺寸 (按照新的布局结构)
        components['preview_frame'].place(x=0, y=0, width=half_width, height=top_height)  # 图像区域占左上角50%×65%
        components['plot_frame'].place(x=half_width, y=0, width=half_width, height=int(top_height * 0.5))  # 折线图区域
        components['analysis_frame'].place(x=half_width, y=int(top_height * 0.5), width=half_width, height=int(top_height * 0.5))  # 分析区域
        components['control_frame'].place(x=0, y=top_height, width=win_width, height=bottom_height)  # 控制按钮区域
        
        # 更新分析标签的wraplength
        components['analysis_label'].config(wraplength=half_width-100)
        
        # 调整内边距和外边距
        components['preview_frame'].configure(padding=int(10 * scale))
        components['plot_frame'].configure(padding=int(10 * scale))
        components['analysis_frame'].configure(padding=int(10 * scale))
        components['control_frame'].configure(padding=int(10 * scale))
        
        # 调整状态框架的边框和内边距
        components['status_frame'].configure(borderwidth=max(int(2 * scale), 1))
        components['status_frame'].configure(padding=int(10 * scale))
        
        # 调整分析显示框架的边框和内边距
        components['analysis_display'].configure(borderwidth=max(int(2 * scale), 1))
        components['analysis_display'].configure(padding=int(5 * scale))
    
    # 初始化按钮样式 - 调整按钮大小以适应7寸屏幕
    style = ttk.Style()
    style.configure('TButton', font=('Arial', 16), padding=8)  # 进一步减小按钮内边距以适应小屏幕
    
    # 绑定窗口大小调整事件
    root.bind('<Configure>', lambda e: root.after(100, adjust_ui_elements))
    
    win_width = 1024  # 窗口宽度
    win_height = 600  # 窗口高度
    half_width = int(win_width * 0.5)  # 50%宽度
    top_height = int(win_height * 0.65)  # 上方区域65%高度
    bottom_height = int(win_height * 0.35)  # 下方区域35%高度
    
    # 重新设计布局框架
    # 图像预览区域 - 占据左上角50%×60%区域
    preview_frame = ttk.Frame(root, padding=10, relief='groove', borderwidth=2)
    preview_frame.place(x=0, y=0, width=half_width, height=top_height)
    components['preview_frame'] = preview_frame
    
    # 折线图区域 - 位于右上角50%×60%区域的上半部分
    plot_frame = ttk.Frame(root, padding=10, relief='groove', borderwidth=2)
    plot_frame.place(x=half_width, y=0, width=half_width, height=int(top_height * 0.5))
    plot_frame.grid_propagate(False)
    components['plot_frame'] = plot_frame
    
    # 分析结果显示区域 - 位于右上角50%×60%区域的下半部分
    analysis_frame = ttk.Frame(root, padding=10, relief='groove', borderwidth=2)
    analysis_frame.place(x=half_width, y=int(top_height * 0.5), width=half_width, height=int(top_height * 0.5))
    components['analysis_frame'] = analysis_frame
    
    # 控制按钮区域 - 位于底部35%区域
    control_frame = ttk.Frame(root, padding=20, relief='groove', borderwidth=2)
    control_frame.place(x=0, y=top_height, width=win_width, height=bottom_height)
    control_frame.grid_propagate(False)
    components['control_frame'] = control_frame

    # 创建状态标签容器
    status_frame = ttk.Frame(control_frame, relief='sunken', borderwidth=2)
    status_frame.pack(pady=5, fill='x', anchor='center')  # 减小垂直内边距
    components['status_frame'] = status_frame
    
    # 创建状态标签 (居中显示)
    status_label = ttk.Label(status_frame, text="就绪", font=('Arial', 20), foreground='green')  # 进一步减小字体大小
    status_label.pack(pady=3, padx=10, fill='both', expand=True)  # 进一步减小垂直内边距
    status_label.configure(anchor='center')  # 居中显示文本
    components['status_label'] = status_label
    
    # 控制按钮 - 使用网格布局以获得更好的控制
    button_frame = ttk.Frame(control_frame)
    button_frame.pack(pady=5, fill='x', expand=True)
    
    start_btn = ttk.Button(button_frame, text='Start Detection', command=start_detection, style='TButton')
    start_btn.grid(row=0, column=0, padx=10, pady=3, sticky='ew')
    
    save_btn = ttk.Button(button_frame, text='Save Results', command=lambda: save_results(last_analysis_result), style='TButton')
    save_btn.grid(row=0, column=1, padx=10, pady=3, sticky='ew')
    
    # 删除结果按钮
    delete_btn = ttk.Button(button_frame, text='Delete Results', command=delete_results, style='TButton')
    delete_btn.grid(row=0, column=2, padx=10, pady=3, sticky='ew')

    load_prev_btn = ttk.Button(button_frame, text='Load Previous', command=load_previous, style='TButton')
    load_prev_btn.grid(row=1, column=0, padx=10, pady=3, sticky='ew')

    load_next_btn = ttk.Button(button_frame, text='Load Next', command=load_next, style='TButton')
    load_next_btn.grid(row=1, column=1, padx=10, pady=3, sticky='ew')

    exit_btn = ttk.Button(button_frame, text='Exit System', command=exit_program, style='TButton')
    exit_btn.grid(row=1, column=2, padx=10, pady=3, sticky='ew')
    
    # 存储按钮引用以便调整大小
    buttons = [start_btn, save_btn, delete_btn, load_prev_btn, load_next_btn, exit_btn]
    components['buttons'] = buttons
    
    # 配置列权重以使按钮均匀分布
    button_frame.grid_columnconfigure(0, weight=1)
    button_frame.grid_columnconfigure(1, weight=1)
    button_frame.grid_columnconfigure(2, weight=1)
    
    # 创建模型分析结果显示标签 - 移到专门的分析框架中
    analysis_display = ttk.Frame(analysis_frame, relief='sunken', borderwidth=2)
    analysis_display.pack(pady=10, fill='both', expand=True)
    components['analysis_display'] = analysis_display
    
    analysis_label = ttk.Label(analysis_display, text="模型分析结果将显示在这里", font=('Arial', 18), foreground='blue', wraplength=half_width-100)
    analysis_label.pack(pady=20, padx=20, fill='both', expand=True)
    components['analysis_label'] = analysis_label
    
    # 创建预览标签
    preview_label = Label(preview_frame)
    preview_label.pack()
    
    # 创建图表
    fig, ax = plt.subplots(figsize=(5, 3))
    ax.set_xlabel('Time (s)')
    ax.set_ylabel('Diameter (px)')
    ax.set_title('Real-time Diameter Measurement')
    ax.grid(True)
    canvas = FigureCanvasTkAgg(fig, master=plot_frame)
    canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
    
    # 创建文件标签（初始隐藏）
    file_label = ttk.Label(root, text="", font=('Arial', 16))
    components['file_label'] = file_label
    
    # 启动摄像头线程 - 优化内存管理
    def camera_thread():
        cap = cv2.VideoCapture(20)
        if not cap.isOpened():
            print("无法打开摄像头")
            return
            
        # 设置摄像头参数以提高性能
        cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
        cap.set(cv2.CAP_PROP_FPS, 30)
        
        frame_count = 0
        last_fps_time = time.time()
        
        try:
            while True:
                ret, frame = cap.read()
                if ret:
                    frame_count += 1
                    
                    # 计算实际FPS
                    if frame_count % 30 == 0:
                        current_time = time.time()
                        fps = 30 / (current_time - last_fps_time)
                        print(f"摄像头FPS: {fps:.1f}")
                        last_fps_time = current_time
                    
                    # 清理旧帧
                    while frame_queue.qsize() > 5:
                        try:
                            frame_queue.get_nowait()
                        except queue.Empty:
                            break
                    
                    # 添加新帧
                    if not frame_queue.full():
                        frame_queue.put(frame)
                    else:
                        # 如果队列满了，移除旧帧并添加新帧
                        try:
                            frame_queue.get_nowait()
                            frame_queue.put(frame)
                        except queue.Empty:
                            frame_queue.put(frame)
                else:
                    print("摄像头读取失败")
                    time.sleep(0.1)
                    
        except Exception as e:
            print(f"摄像头线程错误: {e}")
        finally:
            cap.release()
            cv2.destroyAllWindows()
    
    threading.Thread(target=camera_thread, daemon=True).start()
    
    # 启动处理线程
    threading.Thread(target=process_frame, daemon=True).start()
    
    # 启动预览更新循环
    def preview_loop():
        last_display = None
        def update():
            nonlocal last_display
            try:
                # 非阻塞获取帧（主线程安全）
                display = result_queue.get_nowait()
                update_preview(display)
                last_display = display
            except queue.Empty:
                # 显示最后有效帧或黑色帧
                if last_display is not None:
                    update_preview(last_display)
                else:
                    black_frame = np.zeros((360, 480, 3), dtype=np.uint8)
                    update_preview(black_frame)
            # 按60Hz刷新率调度下一次更新（16ms间隔）
            root.after(16, update)
        # 启动主线程调度的更新循环
        update()
    
    threading.Thread(target=preview_loop, daemon=True).start()
    
    # 启动主窗口
    root.mainloop()
